When performing a penetration test on a .Net Core web site to find security vulnerabilities, some common issues may be found that are not handled by the default .Net Core template in Visual Studio. One of the tools I use to carry out penetration tests in the Zed Attack Proxy (ZAP) that is part of Open Web Application Security Project (OWASP).
Below is a list of some of the common alerts that may be flagged by ZAP or similar tools, and how to fix each one in .Net Core. All fixes are done within the Configure() method of the Startup class. At the end I will also include a code listing of all the fixes together.
X-Frame-Options Header Not Set
Risk: MediumZAP description: X-Frame-Options header is not included in the HTTP response to protect against ‘ClickJacking’ attacks.
Reference: http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx
Fix:
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
await next();
});
Cookie No HttpOnly Flag
Risk: Low
ZAP description: A cookie has been set without the HttpOnly flag, which means that the cookie can be accessed by JavaScript. If a malicious script can be run on this page then the cookie will be accessible and can be transmitted to another site. If this is a session cookie then session hijacking may be possible.
Reference: http://www.owasp.org/index.php/HttpOnly
Fix:
app.UseCookiePolicy(new CookiePolicyOptions
{
HttpOnly = HttpOnlyPolicy.Always
});
Cookie Without Secure Flag
Risk: Low
ZAP description: A cookie has been set without the secure flag, which means that the cookie can be accessed via unencrypted connections.
Reference: http://www.owasp.org/index.php/Testing_for_cookies_attributes_(OWASP-SM-002)
Fix:
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.Always
});
Incomplete or No Cache-control and Pragma HTTP Header Set
Risk: Low
ZAP description: The cache-control and pragma HTTP header have not been set properly or are missing allowing the browser and proxies to cache content.
Reference: https://www.owasp.org/index.php/Session_Management_Cheat_Sheet#Web_Content_Caching
Fix:
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
}
});
app.UseMiddleware<NoCacheMiddleware>();
Add a new Middleware Class:
public class NoCacheMiddleware
{
private readonly RequestDelegate m_next;
public NoCacheMiddleware(RequestDelegate next)
{
m_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Response.OnStarting((state) =>
{
// ref: <a href="http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers">http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers</a>
httpContext.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate");
httpContext.Response.Headers.Append("Pragma", "no-cache");
httpContext.Response.Headers.Append("Expires", "0");
return Task.FromResult(0);
}, null);
await m_next.Invoke(httpContext);
}
}
Web Browser XSS Protection Not Enabled
Risk: Low
ZAP description: Web Browser XSS Protection is not enabled, or is disabled by the configuration of the ‘X-XSS-Protection’ HTTP response header on the web server
Reference:
https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet
https://blog.veracode.com/2014/03/guidelines-for-setting-security-headers/
Fix:
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Xss-Protection", "1");
await next();
});
X-Content-Type-Options Header Missing
Risk: LowZAP description: The Anti-MIME-Sniffing header X-Content-Type-Options was not set to ‘nosniff’. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.
Reference:
http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx
https://www.owasp.org/index.php/List_of_useful_HTTP_headers
Fix:
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
}
});
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
await next();
});
Complete listing
Below is a 'complete listing' combining the individual fixes. I have removed some of the unrelated code to help with readability.Startup class
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseResponseCompression();
loggerFactory.AddLog4Net();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles(new StaticFileOptions
{
//Pen test fix
OnPrepareResponse = ctx =>
{
ctx.Context.Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
ctx.Context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
}
});
app.UseAuthentication();
app.UseSession();
//Pen test fix
app.UseCookiePolicy(new CookiePolicyOptions
{
HttpOnly = HttpOnlyPolicy.Always,
Secure = CookieSecurePolicy.Always
});
//Pen test fix
app.Use(async (context, next) =>
{
context.Response.Headers.Add("X-Frame-Options", "SAMEORIGIN");
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Xss-Protection", "1");
await next();
});
//Pen test fix
app.UseMiddleware<NoCacheMiddleware>();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
Middleware Class:
public class NoCacheMiddleware
{
private readonly RequestDelegate m_next;
public NoCacheMiddleware(RequestDelegate next)
{
m_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Response.OnStarting((state) =>
{
// ref: <a href="http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers">http://stackoverflow.com/questions/49547/making-sure-a-web-page-is-not-cached-across-all-browsers</a>
httpContext.Response.Headers.Append("Cache-Control", "no-cache, no-store, must-revalidate");
httpContext.Response.Headers.Append("Pragma", "no-cache");
httpContext.Response.Headers.Append("Expires", "0");
return Task.FromResult(0);
}, null);
await m_next.Invoke(httpContext);
}
}
Extra: X-Powered-By and Server Headers
The following headers are added by Azure to each request:- X-Powered-By: ASP.NET
- Server: Kestrel
<system.webServer>
<!--<span style="color:#008000;font-family:Consolas;"> Additional entries removed to help readability-->
<security>
<requestFiltering removeServerHeader ="true"></requestFiltering>
</security>
<httpProtocol>
<customHeaders>
<remove name="X-Powered-By"/>
</customHeaders>
</httpProtocol>
</system.webServer></blockquote>